#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
#include "prism/node.h"

static void
pm_node_memsize_node(pm_node_t *node, pm_memsize_t *memsize);

/**
 * Calculate the size of the node list in bytes.
 */
static size_t
pm_node_list_memsize(pm_node_list_t *node_list, pm_memsize_t *memsize) {
    size_t size = sizeof(pm_node_list_t) + (node_list->capacity * sizeof(pm_node_t *));
    for (size_t index = 0; index < node_list->size; index++) {
        pm_node_memsize_node(node_list->nodes[index], memsize);
    }
    return size;
}

/**
 * Append a new node onto the end of the node list.
 */
void
pm_node_list_append(pm_node_list_t *list, pm_node_t *node) {
    if (list->size == list->capacity) {
        list->capacity = list->capacity == 0 ? 4 : list->capacity * 2;
        list->nodes = (pm_node_t **) realloc(list->nodes, sizeof(pm_node_t *) * list->capacity);
    }
    list->nodes[list->size++] = node;
}

PRISM_EXPORTED_FUNCTION void
pm_node_destroy(pm_parser_t *parser, pm_node_t *node);

/**
 * Deallocate the inner memory of a list of nodes. The parser argument is not
 * used, but is here for the future possibility of pre-allocating memory pools.
 */
static void
pm_node_list_free(pm_parser_t *parser, pm_node_list_t *list) {
    if (list->capacity > 0) {
        for (size_t index = 0; index < list->size; index++) {
            pm_node_destroy(parser, list->nodes[index]);
        }
        free(list->nodes);
    }
}

/**
 * Deallocate the space for a pm_node_t. Similarly to pm_node_alloc, we're not
 * using the parser argument, but it's there to allow for the future possibility
 * of pre-allocating larger memory pools.
 */
PRISM_EXPORTED_FUNCTION void
pm_node_destroy(pm_parser_t *parser, pm_node_t *node) {
    switch (PM_NODE_TYPE(node)) {
        <%- nodes.each do |node| -%>
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
        case <%= node.type %>: {
            <%- if node.fields.any? { |field| ![Prism::LocationField, Prism::OptionalLocationField, Prism::UInt8Field, Prism::UInt32Field, Prism::FlagsField, Prism::ConstantField, Prism::OptionalConstantField].include?(field.class) } -%>
            pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node;
            <%- end -%>
            <%- node.fields.each do |field| -%>
            <%- case field -%>
            <%- when Prism::LocationField, Prism::OptionalLocationField, Prism::UInt8Field, Prism::UInt32Field, Prism::FlagsField, Prism::ConstantField, Prism::OptionalConstantField -%>
            <%- when Prism::NodeField -%>
            pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>);
            <%- when Prism::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>);
            }
            <%- when Prism::StringField -%>
            pm_string_free(&cast-><%= field.name %>);
            <%- when Prism::NodeListField -%>
            pm_node_list_free(parser, &cast-><%= field.name %>);
            <%- when Prism::ConstantListField -%>
            pm_constant_id_list_free(&cast-><%= field.name %>);
            <%- else -%>
            <%- raise -%>
            <%- end -%>
            <%- end -%>
            break;
        }
        <%- end -%>
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
        default:
            assert(false && "unreachable");
            break;
    }
    free(node);
}

static void
pm_node_memsize_node(pm_node_t *node, pm_memsize_t *memsize) {
    memsize->node_count++;

    switch (PM_NODE_TYPE(node)) {
        // We do not calculate memsize of a ScopeNode
        // as it should never be generated
        case PM_SCOPE_NODE:
            return;
        <%- nodes.each do |node| -%>
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
        case <%= node.type %>: {
            pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node;
            memsize->memsize += sizeof(*cast);
            <%- if node.fields.any? { |f| f.is_a?(Prism::NodeListField) } -%>
            // Node lists will add in their own sizes below.
            memsize->memsize -= sizeof(pm_node_list_t) * <%= node.fields.count { |f| f.is_a?(Prism::NodeListField) } %>;
            <%- end -%>
            <%- if node.fields.any? { |f| f.is_a?(Prism::ConstantListField) } -%>
            // Constant id lists will add in their own sizes below.
            memsize->memsize -= sizeof(pm_constant_id_list_t) * <%= node.fields.count { |f| f.is_a?(Prism::ConstantListField) } %>;
            <%- end -%>
            <%- node.fields.each do |field| -%>
            <%- case field -%>
            <%- when Prism::ConstantField, Prism::OptionalConstantField, Prism::UInt8Field, Prism::UInt32Field, Prism::FlagsField, Prism::LocationField, Prism::OptionalLocationField -%>
            <%- when Prism::NodeField -%>
            pm_node_memsize_node((pm_node_t *)cast-><%= field.name %>, memsize);
            <%- when Prism::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_node_memsize_node((pm_node_t *)cast-><%= field.name %>, memsize);
            }
            <%- when Prism::StringField -%>
            memsize->memsize += pm_string_memsize(&cast-><%= field.name %>);
            <%- when Prism::NodeListField -%>
            memsize->memsize += pm_node_list_memsize(&cast-><%= field.name %>, memsize);
            <%- when Prism::ConstantListField -%>
            memsize->memsize += pm_constant_id_list_memsize(&cast-><%= field.name %>);
            <%- else -%>
            <%- raise -%>
            <%- end -%>
            <%- end -%>
            break;
        }
        <%- end -%>
#line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>"
    }
}

/**
 * Calculates the memory footprint of a given node.
 */
PRISM_EXPORTED_FUNCTION void
pm_node_memsize(pm_node_t *node, pm_memsize_t *memsize) {
    *memsize = (pm_memsize_t) { .memsize = 0, .node_count = 0 };
    pm_node_memsize_node(node, memsize);
}

/**
 * Returns a string representation of the given node type.
 */
PRISM_EXPORTED_FUNCTION const char *
pm_node_type_to_str(pm_node_type_t node_type)
{
    switch (node_type) {
<%- nodes.each do |node| -%>
        case <%= node.type %>:
            return "<%= node.type %>";
<%- end -%>
    }
    return "";
}
